package com.gui.migrate;

import com.gui.exceptions.RedisEncodingException;
import com.gui.util.Constants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.util.Arrays;

/**
 * Created by Gui on 2017/7/26.
 */
public class RdbParser {

    private static final Logger logger = LoggerFactory.getLogger(RdbParser.class);

    private InputStream input;

    private final int DEFAULT_BUF_SIZE = 8 * 1024;
    private byte[] buf = new byte[DEFAULT_BUF_SIZE];
    private int pos = 0;
    private int validCount = 0;


    public RdbParser(File rdbFile) throws IOException {
        input = new FileInputStream(rdbFile);
    }

    public RdbParser(InputStream inputStream) {
        this.input = inputStream;
    }

    public boolean ready() throws IOException {
        ensureFill();

        byte[] magicBytes = Constants.REDIS_RDB_MAGIC_STR.getBytes();
        for (int i = 0; i < magicBytes.length; i ++) {
            if (magicBytes[i] != buf[pos ++]) {
                logger.error("Redis rdb file magic string is error: {}", new String(buf, 0, magicBytes.length));
                return false;
            }
        }

        String verStr = new String(buf, pos, 4);
        int ver = Integer.parseInt(verStr);
        if (ver < 1 || ver > Constants.REDIS_RDB_VERSION) {
            logger.error("Cannot handle RDB format version {}", ver);
            return false;
        }

        return true;
    }

    public byte[] readNext() throws IOException {

        int expiretime_type;
        int data_type = -1;
        long type;
        long expiretime;
        long dbId = 0;


        ensureFill();
        type = readInteger(1);

        if (type == Constants.REDIS_RDB_OPCODE_EXPIRETIME) {
            expiretime = readInteger(4);
            expiretime_type = Constants.REDIS_TIME_SECOND;

        } else if (type == Constants.REDIS_RDB_OPCODE_EXPIRETIME_MS) {
            expiretime = readInteger(8);
            expiretime_type = Constants.REDIS_RDB_OPCODE_EXPIRETIME_MS;

        } else if (type == Constants.REDIS_RDB_OPCODE_EOF) {
            return null;

        } else if (type == Constants.REDIS_RDB_OPCODE_SELECTDB) {
            dbId = readLength(null);

        } else if (type == Constants.REDIS_RDB_OPCODE_RESIZEDB) {
            long dbSize = readLength(null);
            long expireSize = readLength(null);

        } else if (type == Constants.REDIS_RDB_OPCODE_AUX) {

        }




        return null;
    }


    /** ================================ parse input ======================================= */

    private long readInteger(int len) throws IOException {
        long value = 0;
        for (int i = 0; i < len; i ++) {
            ensureFill();
            value = value << 8;
            value += (buf[pos ++] & 0xFF);
        }
        return value;
    }

    private long readLength(boolean[] isEncoded) throws IOException {
        if (isEncoded != null && isEncoded.length > 0) {
            isEncoded[0] = false;
        }

        ensureFill();
        byte first = buf[pos ++];
        int header = (first & 0xC0) >> 6;
        if (header == 0) {
            return first & 0x3F;
        } else if (header == 1) {
            byte second = buf[pos ++];
            return (first & 0x3F) << 8 + second;
        } else if (header == 2) {
            return readInteger(4);
        } else if (header == 3) {
            if (isEncoded != null && isEncoded.length > 0) {
                isEncoded[0] = true;
            }
            return first & 0x3F;
        } else {
            throw new RedisEncodingException("Unknow length encoding.");
        }
    }

    private boolean[] isEncodedBuf = new boolean[1];
    private String readString() throws IOException {
        long len = readLength(isEncodedBuf);
        if (isEncodedBuf[0]) {
            switch ((int)len) {
                case 0:
                case 1:
                case 2:
                    break;
                case 3:
                    break;
                default:
                    throw new RedisEncodingException("Unkonw RDB encoding type " + len);
                    break;
            }
        }


        String str;


    }


    /** ====================== read data ================================= */

    private void ensureFill() throws IOException {
        if (buf.length - validCount < 2000) {
            //这种情况下，可认为需要接受字节非常多
            buf = Arrays.copyOf(buf, Math.max(buf.length + (buf.length >> 1), len));
        }
        int nRead = input.read(buf, validCount, buf.length - validCount);
        if (nRead > 0) {
            validCount += nRead;
        } else {
            throw new IOException("Unable to read more data");
        }
    }

    private void recycle() {
        int remainCount = validCount - pos;
        boolean oversize = buf.length > DEFAULT_BUF_SIZE * 8;
        boolean resizeBuf = oversize || ((remainCount <= 0) && (buf.length > DEFAULT_BUF_SIZE * 2.5));

        if (resizeBuf) {
            int newSize = Math.max(remainCount, DEFAULT_BUF_SIZE);
            byte[] result = new byte[newSize];
            if (remainCount > 0) {
                System.arraycopy(buf, pos, result, 0, remainCount);
            }
            buf = result;

            pos = 0;
            validCount = remainCount;
        } else if (remainCount <= 0) {
            pos = 0;
            validCount = 0;
        }
    }

}
